package rasterprimate;

import java.io.IOException;
import java.util.ArrayList;

import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.LinearRing;
import com.vividsolutions.jts.geom.Point;
import com.vividsolutions.jts.geom.Polygon;

import repast.simphony.context.Context;
import repast.simphony.context.space.continuous.ContinuousSpaceFactoryFinder;
import repast.simphony.context.space.gis.GeographyFactory;
import repast.simphony.context.space.gis.GeographyFactoryFinder;
import repast.simphony.context.space.grid.GridFactoryFinder;
import repast.simphony.dataLoader.ContextBuilder;
import repast.simphony.engine.environment.RunEnvironment;
import repast.simphony.parameter.Parameters;
import repast.simphony.random.RandomHelper;
import repast.simphony.space.continuous.ContinuousSpace;
import repast.simphony.space.continuous.SimpleCartesianAdder;
import repast.simphony.space.gis.Geography;
import repast.simphony.space.gis.GeographyParameters;
import repast.simphony.space.grid.Grid;
import repast.simphony.space.grid.GridBuilderParameters;
import repast.simphony.space.grid.GridPoint;
import repast.simphony.space.grid.SimpleGridAdder;
import repast.simphony.space.grid.WrapAroundBorders;
import repast.simphony.valueLayer.GridValueLayer;
import utils.PGMReader;
import utils.SimUtils;
import jsc.distributions.Lognormal;


//This file builds the model; creating the environment and populating it with primates
public class Environment implements ContextBuilder<Object>{

	ArrayList<Primate> agents = null;
	ArrayList<PrimateTopo> agentsT = null;
	ArrayList<Resource> resourceList = null;

	private int primateNumber=0;							//used to id the primate within a group
	private double totalRes;								//total resource level
	private double perDiff = 0;

	public Context<Object> build(Context<Object> context){

		System.out.println("Creating the spatial memory model");

		primateNumber=0;

		//create arrayLists of primates and resources
		agents = new ArrayList<Primate>();
		agentsT = new ArrayList<PrimateTopo>();
		resourceList = new ArrayList<Resource>();

		//get/set model parameters
		Constants contants = new Constants();
		int numbGroups = Constants.numberOfGroups;

		//initialize
		totalRes=0;

		//Create Geometry factory; used to create gis shapes (points=primates; polygons=resources)
		GeometryFactory fac = new GeometryFactory();

		// Read in values from a shapefile

		//reads data from a PGM file and inputs that into a matrix
		PGMReader reader = new PGMReader("src/data/mike_foodSites_30m.pgm");
		double matrix[][] = reader.getMatrix();
		double noData = -9999;

		//x and y dims of the map file
		int xdim = reader.xSize;
		int ydim = reader.ySize;

		//corners of map file (lower left)
		double xcoordStart = reader.getTopLeftX();//topleft
		double ycoord= reader.getTopLeftY();//topleft
		double xcoord= reader.getTopLeftX();//topleft
		int cellSize = reader.getCellSize();


		///Create projectoins ********************************************//

		//Create Geography/GIS 
		GeographyParameters<Object> params= new GeographyParameters<Object>();
		GeographyFactory factory = GeographyFactoryFinder.createGeographyFactory(null);
		final Geography geog = factory.createGeography(Constants.GEOG_ID, context, params);
		geog.setCRS("EPSG:21096"); //Arc1960 UTM zone 36N or EPSG::32636

		//continuous space
		final ContinuousSpace<Object> space = ContinuousSpaceFactoryFinder
				.createContinuousSpaceFactory(null)
				.createContinuousSpace(
						Constants.SPACE_ID,
						context,
						new SimpleCartesianAdder<Object>(),
						new repast.simphony.space.continuous.StrictBorders(),
						xdim, ydim);

		//grid space
		final Grid<Object> grid = GridFactoryFinder
				.createGridFactory(null)
				.createGrid(
						Constants.GRID_ID,
						context,
						new GridBuilderParameters<Object>(
								new repast.simphony.space.grid.StrictBorders(),
								new SimpleGridAdder<Object>(),
								true,
								xdim, ydim));

		//grid value layer
		final GridValueLayer foodValueLayer = new GridValueLayer(
				Constants.FoodValueLayer,
				true,
				new repast.simphony.space.grid.StrictBorders(),//WrapAroundBorders()
				xdim, ydim);

		context.addValueLayer(foodValueLayer);


		//Add Resources to the environment *****************************************//

		for (int i = 0; i < ydim; ++i) {
			for (int j = 0; j < xdim; ++j) {
				if (matrix[i][j]!=noData && matrix[i][j]>=0){

					double food = matrix[i][j];//this adds the weight to the landscape
					if (food == 0){
						food = RandomHelper.nextDoubleFromTo(0, Constants.backgroundFood);
					} else{
						food = Math.pow(food, Constants.foodMag);
					}
					totalRes = totalRes+food;

					final Resource cell = new Resource(context,xcoord,ycoord,food,j,i);

					context.add(cell);
					resourceList.add(cell);

					grid.moveTo(cell, j, i);
					foodValueLayer.set(cell.getmaxResLevel(), j, i);
					cell.setBBcell((int)j/5,(int)i/5); //for 5x5 sampling units

				}else{
					//no value in matrix
					System.out.println("Martix of resources returned a NA value");
				}
				//shift ycoord by cell size value
				xcoord=xcoord+cellSize;
			}
			//Set ycoord back to the start and shift xcoord up by cell size value
			xcoord=xcoordStart;
			ycoord = ycoord-cellSize;
		}


		setTotalResource(xdim*ydim);//this adds food to the landscape, weighted by visit rates of the observed group.
		System.out.println("done adding grids from the matrix file: "+resourceList.size());


		//add agents to the landscape
		final double xCenter =  reader.getTopLeftX()+(reader.cellSize*reader.xSize/2.0);
		final double yCenter =  reader.getTopLeftY()-(reader.cellSize*reader.ySize/2.5);


		for (int j=0;j<Constants.groupSize;j++){
			String sex = "male";
			if (RandomHelper.nextDouble()>0.3){ //gender does not play a role in the simulation right now.
				sex="female";
			}


			if (Constants.topoMem==false){
				Coordinate coord = new Coordinate(xCenter+RandomHelper.nextDoubleFromTo(-(30),(30)), yCenter+RandomHelper.nextDoubleFromTo(-30,(30)));
				Primate primateE = new Primate(sex,this,1,Constants.groupSize,primateNumber,coord);
				context.add(primateE);
				agents.add(primateE);
				Point geom = fac.createPoint(coord);
				geog.move(primateE, geom);
				Resource myPatch=SimUtils.findMyCurrentResource(primateE, geog);

				if(myPatch != null){
					space.moveTo(primateE, myPatch.gridX,myPatch.gridY);
					grid.moveTo(primateE,myPatch.gridX,myPatch.gridY);
				} else {
					System.out.println("not added to grid and space as there is no associated resource cell !!");
				}
			} else {
				Coordinate coord = new Coordinate(xCenter+RandomHelper.nextDoubleFromTo(-(30),(30)), yCenter+RandomHelper.nextDoubleFromTo(-30,(30)));
				PrimateTopo primateT = new PrimateTopo(sex,this,1,Constants.groupSize,primateNumber,coord);
				context.add(primateT);
				agentsT.add(primateT);
				Point geom = fac.createPoint(coord);
				geog.move(primateT, geom);
				Resource myPatch=SimUtils.findMyCurrentResource(primateT, geog);

				if(myPatch != null){
					space.moveTo(primateT, myPatch.gridX,myPatch.gridY);
					grid.moveTo(primateT,myPatch.gridX,myPatch.gridY);
				} else {
					System.out.println("not added to grid and space as there is no associated resource cell !!");
				}
			}

			primateNumber++;
		}


		//create a watcher agent; used to keep track of population level variable
		if (Constants.topoMem==true){
			Watcher w = new Watcher(context,geog,agentsT,numbGroups,0);
			context.add(w);	
		}else{
			Watcher w  = new Watcher(context,geog,agents,numbGroups);
			context.add(w);	
		}


		return context;

	}


	private void setTotalResource(int numbCells){
		//calculate the percent difference between the total resource level now and the target level
		double targetRes = numbCells*Constants.resLevel;
		perDiff = totalRes / targetRes;
		double newTotal=0;

		//Divide each resource by the percent difference to make the total equal the target resource amount
		for (Resource r : resourceList){
			r.setmaxResLevel((double)r.getmaxResLevel()/(double)perDiff);
			r.setresLevel((double)r.getresLevel()/(double)perDiff);
			newTotal = newTotal + r.getresLevel();
		}

		//visual check
		//System.out.println("the new total " + newTotal + " should be equal to "+ targetTotalRes+ "   res original "+totalRes);
		//System.out.println("percent diff "+perDiff);

	}

	public ArrayList<Primate> getAgents(){
		return agents;
	}
	public ArrayList<PrimateTopo> getAgentsT(){
		return agentsT;
	}
}
